/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright 2016-2021 NXP
 * Copyright 2021 ACRIOS Systems s.r.o.
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "erpc_threading.h"

#include <errno.h>

#if ERPC_THREADS_IS(RTTHREAD)

using namespace erpc;

////////////////////////////////////////////////////////////////////////////////
// Variables
////////////////////////////////////////////////////////////////////////////////

Thread *Thread::s_first = RT_NULL;
static int thd_idx = 0;

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

Thread::Thread(const char *name)
: m_name(name)
, m_entry(0)
, m_arg(0)
, m_stackSize(0)
, m_priority(0)
, m_thread(0)
, m_next(0)
{
}

Thread::Thread(thread_entry_t entry, uint32_t priority, uint32_t stackSize, const char *name,
               thread_stack_pointer stackPtr)
: m_name(name)
, m_entry(entry)
, m_arg(0)
, m_stackSize(stackSize)
, m_priority(priority)
, m_thread(0)
, m_next(0)
{
}

Thread::~Thread(void)
{
    if (m_thread != RT_NULL) {
        rt_thread_delete(m_thread);
    }
}

void Thread::init(thread_entry_t entry, uint32_t priority, uint32_t stackSize, thread_stack_pointer stackPtr)
{
    m_entry = entry;
    m_stackSize = stackSize;
    m_priority = priority;
    m_stackPtr = stackPtr;
}

void Thread::start(void *arg)
{
    m_arg = arg;
    bool taskCreated = false;

    char name[RT_NAME_MAX];
    rt_snprintf(name, RT_NAME_MAX, "rpc_%d", thd_idx);
    thd_idx++;
    // Enter a critical section to disable preemptive scheduling until we add the newly
    // created thread to the linked list. This prevents a race condition if the new thread is
    // higher priority than the current thread, and the new thread calls getCurrenThread(),
    // which will scan the linked list.
    rt_enter_critical();
    if (m_stackSize < 2048) {
        m_stackSize = 2048;
    }
    if (m_priority < 10) {
        m_priority = 30;
    }

#if ERPC_ALLOCATION_POLICY == ERPC_ALLOCATION_POLICY_STATIC
    if (m_stackPtr != NULL)
    {
        m_thread = rt_malloc(struct rt_thread);
        rt_thread_init(m_thread, (m_name != NULL ? m_name : name), threadEntryPointStub, NULL,
                              m_stackPtr, m_stackSize, m_priority, 10);
        taskCreated = true;
    }
#endif

    if (m_stackPtr == RT_NULL)
    {
        if (RT_NULL !=
            (m_thread = rt_thread_create((m_name != RT_NULL ? m_name : name), threadEntryPointStub, this,
                              m_stackSize, m_priority, 10)))
        {
            taskCreated = true;
        }
    }

    if (taskCreated)
    {
        // Link in this thread to the list.
        if (RT_NULL != s_first)
        {
            m_next = s_first;
        }
        s_first = this;
        rt_thread_startup(m_thread);
    }

    rt_exit_critical();
}

bool Thread::operator==(Thread &o)
{
    return m_thread == o.m_thread;
}

Thread *Thread::getCurrentThread(void)
{
    rt_thread_t thisTask = rt_thread_self();

    // Walk the threads list to find the Thread object for the current task.
    rt_enter_critical();
    Thread *it = s_first;
    while (it != RT_NULL)
    {
        if (it->m_thread == thisTask)
        {
            break;
        }
        it = it->m_next;
    }
    rt_exit_critical();
    return it;
}

void Thread::sleep(uint32_t usecs)
{
    if (usecs > 1000) {
        rt_thread_mdelay(usecs / 1000);
    }
    rt_hw_us_delay(usecs % 1000);
}

void Thread::threadEntryPoint(void)
{
    if (m_entry != RT_NULL)
    {
        m_entry(m_arg);
    }
}

void Thread::threadEntryPointStub(void *arg)
{
    Thread *_this = reinterpret_cast<Thread *>(arg);
    erpc_assert((_this != RT_NULL) && ("Reinterpreting 'void *arg' to 'Thread *' failed." != RT_NULL));
    _this->threadEntryPoint();

    // Remove this thread from the linked list.
    rt_enter_critical();
    Thread *it = s_first;
    Thread *prev = RT_NULL;
    while (it != RT_NULL)
    {
        if (it == _this)
        {
            if (it == s_first)
            {
                s_first = _this->m_next;
            }
            else
            {
                if (prev != RT_NULL)
                {
                    prev->m_next = _this->m_next;
                }
            }
            _this->m_next = RT_NULL;

            break;
        }
        prev = it;
        it = it->m_next;
    }
    rt_exit_critical();
}

static int mtx_idx = 0;
Mutex::Mutex(void)
: m_mutex(0)
{
    char name[RT_NAME_MAX];
    rt_snprintf(name, RT_NAME_MAX, "rpc_%d", mtx_idx);
    mtx_idx++;
    m_mutex = rt_mutex_create(name, RT_IPC_FLAG_PRIO);
}

Mutex::~Mutex(void)
{
    rt_mutex_delete(m_mutex);
}

bool Mutex::tryLock(void)
{
    // Pass a zero timeout to poll the mutex.
    return (RT_EOK == rt_mutex_trytake(m_mutex) ? true : false);
}

bool Mutex::lock(void)
{
    return (RT_EOK == rt_mutex_take(m_mutex, RT_WAITING_FOREVER) ? true : false);
}

bool Mutex::unlock(void)
{
    return (RT_EOK == rt_mutex_release(m_mutex) ? true : false);
}

static int sem_idx = 0;
Semaphore::Semaphore(int count)
: m_sem(0)
{
    char name[RT_NAME_MAX];
    rt_snprintf(name, RT_NAME_MAX, "rpc_%d", sem_idx);
    sem_idx++;
    m_sem = rt_sem_create(name, (rt_base_t)count, RT_IPC_FLAG_PRIO);
}

Semaphore::~Semaphore(void)
{
    rt_sem_delete(m_sem);
}

void Semaphore::put(void)
{
    (void)rt_sem_release(m_sem);
}

bool Semaphore::get(uint32_t timeoutUsecs)
{
    if (timeoutUsecs != kWaitForever)
    {
        if (timeoutUsecs > 0U)
        {
            timeoutUsecs /= 1000U ;
            if (timeoutUsecs == 0U)
            {
                timeoutUsecs = 1U;
            }
            else if (timeoutUsecs > (RT_WAITING_FOREVER / 2))
            {
                timeoutUsecs = (RT_WAITING_FOREVER / 2) - 1;
            }
        }
    }
    else
    {
        timeoutUsecs = RT_WAITING_FOREVER;
    }

    return (RT_EOK == rt_sem_take(m_sem, (rt_tick_t)timeoutUsecs));
}

int Semaphore::getCount(void) const
{
    return static_cast<int>(m_sem->value);
}
#endif /* ERPC_THREADS_IS(FREERTOS) */

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
